/*
 * GeoVISTA Center (Penn State, Dept. of Geography)
 * Copyright (c), 2003-2011,  Jin Chen and Alan M. MacEachren, GeoVISTA Center, Penn State University
 * Licensed under Simplified BSD License
 *
 *  
 *
 * @Original Author: jin Chen
 * @date: Sep 8, 2004$
 * @version: 1.0
 */
package edu.psu.geovista.app.dataloader;

import edu.psu.geovista.app.dataloader.v1.components.MetaDataTable;
import edu.psu.geovista.common.utils.ArrayUtils;
import edu.psu.geovista.common.utils.collection.CollectionUtils;
import edu.psu.geovista.data.model.MetaData;
import edu.psu.geovista.data.model.MetadataModel;
import edu.psu.geovista.data.model.Range;
import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.util.*;

public class DfMetaData extends MetaDataTable {
    public static final boolean DEBUG=false;
    /**
     *  
     *  
     *
     *  
     */
    private Map columnValues=new Hashtable(); // 
    private Map columnDescription=new Hashtable();// 
    //naming convetions for the file
    public static final String Data_FIPS_ColName="FIPS"; //column name of FIPS code in data file
    public static final String MetaFile_ColumnHeader="ColumnHeader";//In meta file, the name of column containing column names
    public static final String MetaFile_Desc="Description";//name of description column in meta file
    public static final String MetaFile_TimeValue="GraphicTimeValue";
    public static final String MetaFile_ObName="ObservationName";
    public static final String MetaFile_Projected="projected";
    public static final String MetaFile_CATEGORY_A="category_a";//Classify columns based on rule A. It may be TYPEB,TYPEC... in the future
    //keys for mata informatoin
    public static final String MetaKey_Projected="Key_projected";
    public static final String MetaKey_ObName="Key_observationName";//observation name

    private String nameColumnHeader=MetaFile_ColumnHeader;
    private String nameDescrition=MetaFile_Desc;
    private String nameProjected=MetaFile_Projected;
    private String nameTimeValue=MetaFile_TimeValue;
    private String nameCategoryA=MetaFile_CATEGORY_A;


    /**
     * add a meta data.
     *  
     * @param meta
     */
    public void add(MetaData meta){
        if(meta!=null){
        Map cd = meta.getColumndescription();

        if(cd!=null){
            this.columnDescription.putAll(cd);
        }
        if (meta instanceof DfMetaData) {
            DfMetaData emeta = (DfMetaData) meta;
            Map cv = emeta.getColumnValues();
            if(cv!=null)
                this.columnValues.putAll(cv );

        }
        Set<MetadataModel> models= meta.getModel();
        this.addDatamodel(models);
        }


        //this.datamodel.
    }

    /**
     * only process the 1st datamodel in the datamodel set since initially meta data only contain 1 datamodel
     * multiple datamodel only happen when merge data
     * @throws UnsupportedOperationException
     */
    public void process() throws UnsupportedOperationException{
        //***
        this.reset();

        //*** Find project and which variable serve as observation name
        //* Find column header ( ID column)

        MetadataModel metamodel = (MetadataModel) this.getModel().toArray()[0];


        if(metamodel ==null)return;
        int indexID = metamodel.getColumnIndexByNameIgnoreCase(this.getNameColumnHeader());//MetaFile_ColumnHeader);
        if(indexID<0){
            //not found the column
            String msg=getColumnNotFoundErrorMsg(this.getNameColumnHeader());//MetaFile_ColumnHeader);
            throw new UnsupportedOperationException(msg);
        }
        //* Find Description column
        int indexDesc = metamodel.getColumnIndexByNameIgnoreCase(this.getNameDescrition());//MetaFile_Desc);
        if(indexDesc<0){
             String msg=getColumnNotFoundErrorMsg(getNameDescrition());//MetaFile_Desc);
            throw new UnsupportedOperationException(msg);
        }

       // int indexCtgA = this.datamodel.getColumnIndexByNameIgnoreCase(MetaFile_CATEGORY_A);



        Object[] idCol = metamodel.getColumnAsArray(indexID);
        Object[] descpColumn = metamodel.getColumnAsArray(indexDesc);
        processBasic(idCol, descpColumn);



        //*** Time Series Data
        extractTsValues(metamodel);// 

       //   
        // Hashtable desc = this.getMappingForColumn(this.getNameColumnHeader() , this.getNameDescrition());//MetaFile_ColumnHeader,MetaFile_Desc);
        //this.setColumnDescription(desc);

        //this.getColumndescription() ;//fill columnDescription Hashtable

    }
   // 
    public void processBasic(Object[] idCol, Object[] descpColumn) {
        //* Go through ID column and Description column

        for (int row =0;row <descpColumn.length ;row++){
            String des=(String) descpColumn[row];    //a cell of Description Column

            //. Find if the shp data is projected or not
            if(indexOfStringIgnoreCase(des,getNameProjected()) >=0){//MetaFile_Projected
                //dp{
                if (DEBUG){
                    System.out.println("projected data");
                }//dp}

               setMetaData(MetaKey_Projected,Boolean.TRUE );
            }

            //. Find a single column as observation name
            if(indexOfStringIgnoreCase(des.trim(),MetaFile_ObName)>=0){

                String obName=(String) idCol[row];
                setMetaData(MetaKey_ObName,new String[]{obName}) ;
                //dp{
                if (DEBUG){
                    System.out.println("ob name:"+obName);
                }//dp}


            }
        }

        //find multiple observation names
        String[] obnames=new String[0];
        for (int row =0;row <descpColumn.length ;row++){
            String id=(String) idCol[row];
            String des=(String) descpColumn[row];
            if(indexOfStringIgnoreCase(id,MetaFile_ObName)>=0){
                //String obNames=(String) descpColumn[row];
                obnames = getStringTokens(des, ",");
                break;
            }
        }
        //verify  the columnn do exist in idCol.
        Vector v=new Vector();
        for (int i=0;i<obnames.length ;i++){
            if(ArrayUtils.contain(idCol,obnames[i])){
               v.add(obnames[i]);
            }
            else{
                System.err.println(this.getClass().getName() + " The attribute "+obnames[i]+ " does not exist in the Columnheader column of meta data file");
            }
        }
        obnames= CollectionUtils.convert2StringArrayNoNull(v);
        if(obnames.length >0){
            setMetaData(MetaKey_ObName,obnames) ;
        }

       Hashtable desc = this.getMappingForColumn(this.getNameColumnHeader() , this.getNameDescrition());//MetaFile_ColumnHeader,MetaFile_Desc);
       this.setColumnDescription(desc);
    }
    public String[] getObservationNames(){
        //MetaKey_ObName
        Object obnames = this.getMetaData(MetaKey_ObName);
        if(obnames instanceof String )return new String[]{(String)obnames};//single observation name
        else if(obnames instanceof String[])  return (String[])obnames;
        else return new String[0];
    }
    private String[] getStringTokens(String s,String delimit){
        StringTokenizer token=new StringTokenizer(s,delimit);
        Vector v=new Vector(token.countTokens() );
        while(token.hasMoreTokens()){
            String tk = token.nextToken().trim() ;
            v.add(tk);
        }
        return CollectionUtils.convert2StringArrayNoNull(v);


    }
    /**
     * Time series value. Convert string value to Integer
     * For backward compatible
     *
     */
    private  void extractTsValues( MetadataModel datamodel ){
       int indexTimeSeries = datamodel.getColumnIndexByNameIgnoreCase(this.getNameTimeValue());//MetaFile_TimeValue);

        if (indexTimeSeries>=0) {//there is time series value
            if(this.columnValues==null){
            columnValues=new Hashtable();
            }
            columnValues.clear();//clear values previously hold

            int indexKey = datamodel.getColumnIndexByNameIgnoreCase(this.getNameColumnHeader());//MetaFile_ColumnHeader);
            //int indexVal = this.datamodel.getColumnIndexByNameIgnoreCase(valueCol);

            Object[] keyColumn = datamodel.getColumnAsArray(indexKey);
            Object[] valColumn = datamodel.getColumnAsArray(indexTimeSeries);
            for (int i=0;i<keyColumn.length ;i++){
                Object id = keyColumn[i];
                String  tv = (String) valColumn[i];
                if(!tv.equals("") ){
                                    try {
                                        /*int itv=Integer.parseInt(tv);
                                        Integer value=new Integer(itv);*/
                                        float itv=Float.parseFloat(tv);
                                        Float value=new Float(itv);

                                        if(itv>0&&itv<1000000){
                                            //null value = Integer.Min; tsValue[i]<1000000 to avoid nonsense value
                                            columnValues.put(id ,value);

                                        }

                                        //dp{
                                        if (DEBUG){
                                            System.out.println("Time Value:["+id+":"+itv+"]");
                                        }//dp}

                                    } catch (NumberFormatException e) {
                                        //this element is not number. go ahead
                                        //e.printStackTrace();  //To change body of catch statement use Options | File Templates.
                                    }

                                }

            }//for

            }

    }
     private static String getColumnNotFoundErrorMsg(String colName ){
        //String msg= "Can't find a column named "+MetaData.MetaFile_Desc+" in "+fn+". \n Some functions will be turned off or the Meta file is ignored. \n For detail, please ask your data provider.";
        String msg= "The Wizard cannot locate  a column named '"+colName+"'";//+"' in "+fn+". \n Some functions will be turned off or the Meta file is ignored. \n For detail, please ask your data provider.";

        return msg;
    }
    /*******************************************************************************************************
     * service:
     *       although u can get data by access datamodel, requirement of clinet programs are more specific
     *******************************************************************************************************/
     /**************************************************
      *                time series
      ***************************************************/
     /**
     * Set time series value. It allow users to manually set time series value
     * @param colID  column ID
     * @param value
     */
    public void setColumnValue(String colID, float value) {
        this.columnValues.put(colID,new Float(value));
    }
   /* public void setColumnValues(Hashtable columnValues) {
        //Collections.
        this.columnValues = (Hashtable) columnValues.clone() ;
    }*/
    /**
     * key : colID, value: column vlaue
     * @return
     */
    public Map getColumnValues() {
        return (Map) ((Hashtable) columnValues).clone();
    }
    public List getTimeValues(String[] columnNames){
        ArrayList tvs=new ArrayList(columnNames.length );
        for (int i=0;i<columnNames.length ;i++){
            String col = columnNames[i];
            Object value = this.columnValues.get(col);
            tvs.add(value);
        }
        return tvs;
    }
    public List<String> getCategoryA(String[] columnNames){
        List ctgs=new Vector( );
        Hashtable ctga = getMappingForColumn(this.getNameColumnHeader(),this.getNameCategoryA());//MetaFile_ColumnHeader, MetaFile_CATEGORY_A);
        if(ctga.size() <=0)return ctgs;
        for (int i=0;i<columnNames.length;i++){
            String  ctg = (String) ctga.get(columnNames[i]);
            ctg= isValid(ctg)?ctg:"";
            ctgs.add(ctg);
        }
        return ctgs;
    }

    public static boolean isValid(String s){
        //org.apache.commons.lang.StringUtils.isBlank(s);
        //org.apache.tools.ant.util.StringUtils.
       // Textu
        if(s!=null &&s.trim().length() >0){
            return true;
        }
        return false;
    }
    public String[] getTimeValuesAsString(String[] colnames){

        List timevalues = getTimeValues(colnames);  // A column
        if(timevalues!=null||timevalues.size() >0){
           return CollectionUtils.convert2StringArrayNoNull(timevalues);
        }
        return new String[0];
    }
    public void setColumnValues(Map columnValues) {
        this.columnValues = columnValues;
    }

     /**
     *
     * @return  min and max of column value
     */
    public Range getColumnValuesExtreme() {
        if(columnValues==null||columnValues.size() <=0) return null;
        //Enumeration e=this.columnValues.elements() ;
         Collection e = columnValues.values();
         ArrayList l=new ArrayList(e);
        Collections.sort(l);
        Float max=(Float) l.get(l.size() -1);
        Float min=(Float) l.get(0);
        Range ext=new Range(max.floatValue() ,min.floatValue() );
        return ext;
    }

    /**************************************************
     *                column description
     ***************************************************/
    public Map getColumndescription() {
        if(this.columnDescription ==null||columnDescription.size() ==0){
             columnDescription= this.getMappingForColumn(this.getNameColumnHeader() ,this.getNameDescrition());//MetaFile_ColumnHeader,MetaFile_Desc);
        }
         return columnDescription;
    }

    public void setColumnDescription(Map columnDescription) {
        this.columnDescription = columnDescription;
    }

    public String getColumnDescription(String colID) {
        Map columndescription = getColumndescription();
        return (String) columndescription.get(colID);
    }
    public String[] getColumnDescriptions(String[] attrnames){
        String[] descs=new String[attrnames.length];
            for (int i=0;i<attrnames.length;i++){
                 descs[i] = this.getColumnDescription(attrnames[i]);
            }
        return descs;
    }



     /**
     * Search  key in the string <s>. Ignore case of both
     * @param s
     * @param key
     * @return
     */
    private static int indexOfStringIgnoreCase(String s,String key){
         if(s==null ||key==null)return -1;
        String mys=s.toLowerCase() ;
        String mykey=key.toLowerCase() ;
        return mys.indexOf(mykey);
    }
    //shallow copy, kee datamodel instead of copying it
    public Object clone() {
        //Hashtable columnDespt=(Hashtable) this.getColumndescription().clone();
        DfMetaData meta=new DfMetaData();
        try {
            // 
            // 
            BeanUtils.copyProperties(meta,this);

        } catch (IllegalAccessException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (InvocationTargetException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        // 
        //not copy datamodel
        Set<MetadataModel> datamodels = this.getModel();
        meta.addDatamodel(datamodels );
        meta.setColumnDescription(this.columnDescription );// 
        meta.setColumnValues(this.columnValues);
        return meta;
    }

    

    public String getNameColumnHeader() {
        return nameColumnHeader;
    }

    public void setNameColumnHeader(String nameColumnHeader) {
        this.nameColumnHeader = nameColumnHeader;
    }

    public String getNameDescrition() {
        return nameDescrition;
    }

    public void setNameDescrition(String nameDescrition) {
        this.nameDescrition = nameDescrition;
    }

    public String getNameProjected() {
        return nameProjected;
    }

    public void setNameProjected(String nameProjected) {
        this.nameProjected = nameProjected;
    }

    public String getNameTimeValue() {
        return nameTimeValue;
    }

    public void setNameTimeValue(String nameTimeValue) {
        this.nameTimeValue = nameTimeValue;
    }

    public String getNameCategoryA() {
        return nameCategoryA;
    }

    public void setNameCategoryA(String nameCategoryA) {
        this.nameCategoryA = nameCategoryA;
    }


}
